#!/usr/bin/ruby

require 'osx/cocoa'

require 'rubygems'
require 'mongrel'
require 'fileutils'

require 'artist'
require 'playlist'
require 'templates'

include OSX

OSX.require_framework 'ScriptingBridge'

if !defined?(TunesFastForwarding)
TunesFastForwarding = 1800426310
TunesPaused = 1800426352
TunesPlaying = 1800426320
TunesRewinding = 1800426322
TunesStopped = 1800426323
end

appSupDir = OSX::NSSearchPathForDirectoriesInDomains(OSX::NSApplicationSupportDirectory, OSX::NSUserDomainMask, true )
DOC_ROOT  = "#{appSupDir[0]}/TunesRemote/htdocs"
PID_FILE = "#{appSupDir[0]}/TunesRemote/pid.txt"
PORT_FILE = "#{appSupDir[0]}/TunesRemote/port.txt"
system "mkdir -p \"#{DOC_ROOT}\""

EXP_ROOT  = "#{appSupDir[0]}/TunesRemote"


class Array
  def shuffle
    array = dup
    size.downto 2 do |j|
      r = rand j
      array[j-1], array[r] = array[r], array[j-1]
    end
    array
  end
end


	


class ITunesServer < Mongrel::HttpHandler

    @@tunesApp = SBApplication.applicationWithBundleIdentifier_("com.apple.iTunes")
    @@tunesApp.activate
    @@tunesApp.browserWindows[0].visible = false
	
	@@totalTracks = 0
	@@tunesScanned = false
	@@server = nil
	@@artwork_cache_size = 75
	
	@playThread = nil
	@nextTrack = false
	@pause = false
	

	def self.startServer(port)
	
		system "unzip -o -u #{File.expand_path( File.dirname(__FILE__) ) }/htdocs.zip -d '#{EXP_ROOT}'"
		
		if( @@tunesScanned == false )
			ITunesServer.scanTunes
		end
		
		@@server = Mongrel::HttpServer.new("0.0.0.0", port)
		@@server.register("/tunes", ITunesServer.new)
		@@server.register("/", Mongrel::DirHandler.new(DOC_ROOT + "/", false, "index.html"))
		
		return @@server.run
	end


	def self.stopServer
		if(@@server)
			@@server.stop
			@@server = nil
		end
	end
	
    def gen_album_art(width, height)
        # construct a server-side filename for the artwork
        # using the track name and artist name
        currentTrack = @@tunesApp.currentTrack
        fileName = CGI.escapeHTML(CGI.escape("#{currentTrack.artist.to_s}__#{currentTrack.album.to_s}.jpg".split.join))
        localFile = ("#{DOC_ROOT}/albumArt/" + fileName)
        artURL = File.join('/albumArt/', CGI.escape(fileName))
		
			   
		if File.exists?(localFile) # the artwork already exists; no need to generate a new file
            return artURL
        end

        # before generating this new album art, clean out the server-side artwork cache
        images = Dir.glob(File.join("#{DOC_ROOT}/albumArt/", "*#{File.extname(fileName)}"))
        if images.length >= @@artwork_cache_size
            images.each { |entry|
                File.delete(File.expand_path(entry))
            }
        end

        # Get album art from iTunes and write it to a URL that the web app can fetch
        if currentTrack.artworks
            # iTunes may have multiple artworks associated with the track; just use the first one
            currentTrack.artworks.each do |artwork|
                if artwork
                    # use NSImage via RubyCocoa to write out a scaled copy of the artwork
                    # an active NSApplication instance is needed before NSImage can be used
                    NSApplication.sharedApplication
                    size = NSSize.new(width, height)
                    smallImage = NSImage.alloc.initWithSize(size)
                    smallImage.lockFocus
                    
                    # scale the artwork by drawing it into a new image with the desired size
                    artwork.data.drawInRect_fromRect_operation_fraction(NSRect.new(0, 0, size.height, size.width), NSZeroRect, NSCompositeCopy, 1.0)
                    smallImage.unlockFocus
                    
                    # write the image to disk and return the URL if successful
                    jpegRep = NSBitmapImageRep.imageRepWithData(smallImage.TIFFRepresentation)
                    jpegData = jpegRep.representationUsingType_properties(NSJPEGFileType, nil)
                    if jpegData.writeToFile_atomically(localFile, false)
                        return artURL
                    end
                end
            end
        end
        
        return nil
    end

	def playerState
	  s="<Tunes> <player state="
	  case @@tunesApp.playerState
		when TunesPlaying
		  s << "\"playing\""
		when TunesPaused
		  s << "\"paused\""
		when TunesStopped
		  s << "\"stopped\""
	  end
	  
	  percent = 0
	  currentTrack = @@tunesApp.currentTrack
	  if( currentTrack && currentTrack.exists )
		  percent = ((100 * @@tunesApp.playerPosition) / currentTrack.duration).to_i
	  end

	  s << " album=\"#{@currentAlbumS}\""
	  s << " position=\"#{percent.to_s}\""
	  s << " artist=\"#{@currentArtistS}\""
	  s << " artURL=\"#{@currentPictureS}\""
	  s << " trackname=\"#{@currentSongS}\""

	  
	  s << " /> </Tunes> "
	  return s
	  
	end
	
	def self.scanTunes
	
	  Artist.all.clear
	  Album.all.clear
	  PlayList.all.clear

	  ( 1 ... @@tunesApp.sources[0].userPlaylists.length ).each do |p|
		nm = @@tunesApp.sources[0].userPlaylists[p].name
		pl = PlayList.new(nm.to_s)
		playList = @@tunesApp.sources[0].userPlaylists[p]
		tracks = playList.tracks
		tracks.each do |t|
			pl.addTrack(t)
		end
	  end


	  fl = File.new("#{DOC_ROOT}/index.html","w")
	  s = IdexPage1.gsub('@@_machine_name@@', `hostname`)
	  s.gsub!("@@MainLists@@",MainLists)
	  s.gsub!("@@albumName@@","''")
	  fl.puts s.gsub('@@player@@',"")

	  PlayList.all.each do |a|
		s = ArtistTpl.gsub("artist_ref","/tunes/playList?name=#{CGI::escape(a.name)}")
		s.gsub!("artist_name",a.name)
		fl.puts s
	  end

	  fl.puts IdexPage2
	  fl.close

	  
	  playList = @@tunesApp.sources[0].libraryPlaylists[0]
	  tracks = playList.tracks
	  @@totalTracks = tracks.length

	  tracks.each do |t|
		art = Artist.artistWith(t.artist.to_s)
		art.each do |a|
			a.addTrack(t)
		end
	  end

	  fl = File.new("#{DOC_ROOT}/artists.html","w")
	  s = IdexPage1.gsub('@@_machine_name@@', `hostname`)
	  s.gsub!("@@MainLists@@","")
	  s.gsub!("@@albumName@@","''")
	  fl.puts s.gsub('@@player@@',"")

	  Artist.all.sort.each do |a|
		s = ArtistTpl.gsub("artist_ref","/tunes/artist?name=#{CGI::escape(a.name)}")
		s.gsub!("artist_name",a.name)
		fl.puts s
	  end

	  fl.puts IdexPage2
	  fl.close

	  fl = File.new("#{DOC_ROOT}/albums.html","w")
	  s = IdexPage1.gsub('@@_machine_name@@', `hostname`)
	  s.gsub!("@@MainLists@@","")
	  s.gsub!("@@albumName@@","''")
	  fl.puts s.gsub('@@player@@',"")

	  Album.all.sort.each do |a|
		s = ArtistTpl.gsub("artist_ref","/tunes/album?name=#{CGI::escape(a.name)}")
		s.gsub!("artist_name",a.name)
		fl.puts s
	  end

	  fl.puts IdexPage2
	  fl.close
	  
	  @@tunesScanned = true
	  
	  tracks[0].playOnce(true);
	  sleep(0.2)
	  @@tunesApp.stop
	  
	end
	

	def initialize
	
	  @currentAlbum = nil
	  @currentArtist = nil
	  @currentPlaylist = nil
	  @currentPictureS  = "/albumArt/default.png"
	  @currentSongS  = "-"
	  @currentAlbumS  = "-"
	  @currentArtistS  = "-"

	  puts "starting web server..."
	end
         
    # send commands to iTunes and check state against imported constants
    def process(req, resp)
        resp.start(200) do |head,out|
            # check for iTunes commands sent by the web application
            # parse the URL query string for appropriate command tokens
            req_uri = req.params["QUERY_STRING"]
            argHash = Mongrel::HttpRequest.query_parse(req_uri)
            
            puts req.params['REQUEST_URI']
			
            case req.params['REQUEST_URI']
			  when /\/nowPlaying/
				  ct, c = processPlaying(argHash)
                  head['Content-Type'] = ct
				  out.write(c)
              when /\/artist/
                  ct, c = processArtist(argHash)
                  head['Content-Type'] = ct
                  out.write(c)
              when /\/playList/
                  ct, c = processList(argHash)
                  head['Content-Type'] = ct
                  out.write(c)
              when /\/album/
                  ct, c = processAlbum(argHash)
                  head['Content-Type'] = ct
                  out.write(c)
              when /\/control/
                  ct, c = processControl(argHash)
                  head['Content-Type'] = ct
                  out.write(c)
            end

        end
    end
	
	
	
	def	processPlaying(args) 
	
	  s = PlayingPage.gsub('@@_machine_name@@', @@tunesApp.currentTrack.name.to_s)
	  s.gsub!("@@picture@@",@currentPictureS);
	  s.gsub!("@@song@@",@currentSongS);
	  s.gsub!("@@album@@",@currentAlbumS);
	  s.gsub!("@@artist@@",@currentArtistS);

	  if(@currentAlbum)    
		s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","album=#{@currentAlbumS}"))
	  end
	  
	  if(@currentArtist)
		s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","artist=#{@currentArtistS}")) 
	  end
	  
	  if(@currentPlaylist)
		s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","list=#{@currentPlaylist.name}")) 
	  else
		s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","album=#{@currentAlbumS}"))
	  end

	  return 'text/html', s
	  
	end
    
    
    def processAlbum(args)

        artist = nil

        artist = Artist.artist( args['artist'] ) if args['artist']
        if( artist )
          alb = artist.album( args['name'] )
		  puts "args['name'] = #{args['name']}"
		  puts "processAlbum artist: #{alb.name}"
        else
          alb = Album.album( args['name'] )
		  puts "args['name'] = #{args['name']}"
		  puts "processAlbum album: #{alb.name}"
        end
        
        s = IdexPage1.gsub('@@_machine_name@@', alb.name)
		s.gsub!("@@MainLists@@","")
        s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","album=#{CGI::escape(alb.name)}"))
        alb.tracks.each_with_index do |t,i|
          s1 = ArtistTpl.gsub("artist_ref","javascript:playFromTrack(#{i.to_s});")
          s1.gsub!("artist_name",t.name)
          s += s1
        end
        s += IdexPage2
        
        @currentAlbum = alb
		@currentArtist = nil
		@currentPlaylist = nil

        return 'text/html', s

    end
	
    
    def processArtist(args)
        artist = Artist.artist( args['name'] )
        s = IdexPage1.gsub('@@_machine_name@@', artist.name)
		s.gsub!("@@MainLists@@","")
        s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","artist=#{CGI::escape(artist.name)}"))
        artist.albums.each do |a|
          s1 = ArtistTpl.gsub("artist_ref","album?name=#{CGI::escape(a.name)}&artist=#{args['name']}")
          s1.gsub!("artist_name",a.name)
          s += s1
        end
        s += IdexPage2
        
        return 'text/html', s
    end
    
    def processList(args)
        list = PlayList.playList(CGI::unescape(args['name']))
        s = IdexPage1.gsub('@@_machine_name@@', list.name)
		s.gsub!("@@MainLists@@","")
        s.gsub!("@@player@@",PlayerTpl.gsub("@@playArgs@@","list=#{CGI::escape(list.name)}"))
        list.tracks.each_with_index do |t,i|
          s1 = ArtistTpl.gsub("artist_ref","javascript:playFromTrack(#{i.to_s});")
          s1.gsub!("artist_name",t.name)
          s += s1
        end
        s += IdexPage2
        
        return 'text/html', s
    end
    

	def processPlayWhat(args)

		if( args['album'] )
			alb_name = CGI::unescape(args['album'])
			if( @currentAlbum && @currentAlbum.name == alb_name )
				return
			end
			@currentAlbum = Album.album(alb_name)
			@currentArtist = nil
			@currentPlaylist = nil
		end

		if( args['list'] )
			lst_name = CGI::unescape(args['list'])
			if( @currentPlaylist && @currentPlaylist.name == lst_name )
				return
			end
			@currentAlbum = nil
			@currentArtist = nil
			@currentPlaylist = PlayList.playList(lst_name)
		end

		if( args['artist'] )
			art_name = CGI::unescape(args['artist'])
			if( @currentArtist && @currentArtist.name == art_name )
				return
			end
			@currentAlbum = nil
			@currentArtist = Artist.artist(art_name)
			@currentPlaylist = nil
		end
	end




    def processControl(args)

      case args['op'] 
	  
        when "stats"
		  
          return 'text/plain', "artists:  #{Artist.all.length}\nalbums:  #{Album.all.length}\ntracks:  #{@@totalTracks}"
		  
        when "next"
          do_next
		  
        when "pause"
          do_pause
		  
        when "play"
		
		  processPlayWhat(args)
		  
		  if(@currentAlbum)  
				puts "Play album: #{@currentAlbum.name} "
			do_play_album(false)
		  end
		  
		  if(@currentArtist)
			puts "Play artist: #{@currentArtist.name} "
			do_play_artist(false) 
		  end
		  
		  if(@currentPlaylist)
			puts "Play playlist: #{@currentPlaylist.name} "
			do_play_list(false) 
		  end
		  
        when "shuffle"
          
		  processPlayWhat(args)

		  if(@currentAlbum)    
			puts "Shuffle album: #{@currentAlbum.name} "
			do_play_album(true)
		  end
		  
		  if(@currentArtist)
			puts "Shuffle artist: #{@currentArtist.name} "
			do_play_artist(true) 
		  end
		  
		  if(@currentPlaylist)
			puts "Shuffle playlist: #{@currentPlaylist.name} "
			do_play_list(true) 
		  end
		  
        when "stop"
          do_stop
		  
        when "playFrom"
		  processPlayWhat(args)
          playFrom(args["track"].to_i)

      end

      return 'text/xml', playerState
      
    end

	def do_next
		if( @playThread != nil )
			@nextTrack = true
		end
	end

	def do_stop
		if( @playThread != nil )
			@playThread.kill!
			@playThread = nil
		end
		@@tunesApp.stop
	    @currentPictureS  = "/albumArt/default.png"
	    @currentSongS  = ""
	    @currentAlbumS  = ""
	    @currentArtistS  = ""
	end

	def do_pause
		if( @playThread != nil )
			if(@pause)
				@pause = false
			else
				@pause = true
			end
		end
	end

	def doPlay(trackNum)
		if( @playThread != nil )
			@playThread.kill!
		end

		@nextTrack = false
		@pause = false

		@playThread = Thread.new do
			(trackNum ... @playTracks.length).each do |i|
				t = @playTracks[i]
				t.playOnce(true)

			    @currentSongS     = (@@tunesApp.currentTrack.name.to_s)
			    @currentArtistS   = (@@tunesApp.currentTrack.artist.to_s)
			    @currentAlbumS    = (@@tunesApp.currentTrack.album.to_s)
			    @currentPictureS  = gen_album_art(180, 180) || "/albumArt/default.png"

				while( @@tunesApp.playerState == TunesPlaying )
					sleep(1)
					if( @nextTrack == true )
						@nextTrack = false
						break; #while
					end
					if( @pause == true )
						@@tunesApp.pause
						while( @pause == true )
						  sleep(1)
						end
						t.playOnce(true)
					end
				end
			end
		end
	end

	def playFrom(trackNum)
		if( @currentAlbum )
			do_play_album(false,trackNum)
		end 
		if( @currentArtist )
			do_play_artist(false,trackNum)
		end
		if( @currentPlaylist )
			do_play_list(false,trackNum)
		end
	end

	def do_play_album(shuffle, track=0)
		if( @currentAlbum )
			if( shuffle )
				do_stop
				@playTracks = @currentAlbum.tracks.shuffle
			else
				@playTracks = @currentAlbum.tracks
			end
			doPlay(track)
		end
	end

	def do_play_artist(shuffle, track=0)
		if( @currentArtist )
			@playTracks = []
			@currentArtist.albums.each do |a|
				a.tracks.each do |t|
					if( t.artist.downcase.index(@currentArtist.name.downcase) != nil)
						@playTracks << t
					end
				end
			end
			if( shuffle ) 
				do_stop
				@playTracks = @playTracks.shuffle
			end
			@playTracks.each do |t|
				puts "#{t.name}; #{t.artist}"
			end
			doPlay(track)
		end
	end

	def do_play_list(shuffle, track=0)
		if( @currentPlaylist )
			@playTracks = []
			@currentPlaylist.tracks.each do |a|
				@playTracks << a
			end
			if( shuffle ) 
				do_stop
				@playTracks = @playTracks.shuffle
			end
			doPlay(track)
		end
	end

end


if $0 == __FILE__ then

	port = 0

	if( ARGV[0] )
		port = ARGV[0].to_i
	else 
		if File.file?(PORT_FILE)
			port = IO.read(PORT_FILE).to_i rescue nil
		end
	end
	
	if( port < 1024	) 
		port = 2000
	end

	Dir.chdir( File.expand_path( File.dirname(__FILE__) ) )


    if File.file?(PID_FILE)
		p = IO.read(PID_FILE).to_i rescue nil
		if(p)
			puts "Killing process #{p}..."
			begin
				Process.kill("TERM", p) 
			rescue
				FileUtils.rm(PID_FILE) 
			end
		end
		while File.file?(PID_FILE)
			sleep(1)
		end
	end

	begin 
		Signal.trap("TERM") do
			puts "stopping server.."
			ITunesServer.stopServer
			exit 0
		end
		Signal.trap("INT") do
			puts "stopping server.."
			ITunesServer.stopServer
			exit 0
		end
		File.open(PID_FILE, 'w') {|f| f << Process.pid }
		ITunesServer.startServer(port).join
	ensure
		FileUtils.rm(PID_FILE) 
	end
end


